package net.lab0.fractal.abstracts;


import javax.swing.SwingUtilities;
import javax.swing.event.EventListenerList;

import net.lab0.fractal.classes.ViewCoordinates;
import net.lab0.fractal.event.FractalStateEvent;
import net.lab0.fractal.event.FractalStateListener;


public abstract class AbstractManageableUI2DFractal
extends AbstractUI2DFractal
implements Runnable, ManageableFractalInterface
{
	private FractalState		state				= FractalState.NOT_STARTED;
	private Thread				thread;
	private EventListenerList	eventListenerList	= new EventListenerList();
	
	public AbstractManageableUI2DFractal(ViewCoordinates viewCoordinates, long maxIteration, float progressUpdateInterval,
	long timeUpdateInterval)
	{
		super(viewCoordinates, maxIteration, progressUpdateInterval, timeUpdateInterval);
	}
	
	@Override
	public FractalState getState()
	{
		return state;
	}
	
	@Override
	public void pauseComputation()
	{
		if (state.equals(FractalState.RUNNING))
		{
			state = FractalState.PAUSED;
			fireStateEvent();
		}
	}
	
	@Override
	public void startComputation()
	{
		if (thread == null && state.equals(FractalState.NOT_STARTED))
		{
			thread = new Thread(this);
			thread.start();
			state = FractalState.RUNNING;
			fireStateEvent();
		}
	}
	
	@Override
	public void stopComputation()
	{
		if (state.equals(FractalState.RUNNING) || state.equals(FractalState.PAUSED))
		{
			state = FractalState.STOPPING;
			fireStateEvent();
		}
	}
	
//	@Override
	public Thread getThread()
	{
		return thread;
	}
	
	@Override
	public void unpauseComputation()
	{
		if (state.equals(FractalState.PAUSED))
		{
			state = FractalState.RUNNING;
			synchronized (thread)
			{
				thread.notifyAll();
			}
			fireStateEvent();
		}
	}
	
	@Override
	public void restartComputation()
	{
		if (state.equals(FractalState.RUNNING) || state.equals(FractalState.PAUSED))
		{
			state = FractalState.RESTARTING;
			fireStateEvent();
			synchronized (thread)
			{
				thread.notifyAll();
			}
		}
	}
	
	@Override
	public final void run()
	{
		while (!state.equals(FractalState.STOPPING))
		{
			setProgress(0f);
			
			pauseTest();
			
			doComputation();
			
			if (!restartTest())
			{
				state = FractalState.PAUSED;
				fireProgressEvent(true);
				fireStateEvent();
			}
			else
			{
				state = FractalState.RUNNING;
				fireStateEvent();
			}
		}
		state = FractalState.FINISHED;
		fireStateEvent();
	}
	
	@Override
	public void setMaxIteration(long maxIteration)
	{
		super.setMaxIteration(maxIteration);
		restartComputation();
	}
	
	public void addFractalStateListener(FractalStateListener listener)
	{
		eventListenerList.add(FractalStateListener.class, listener);
	}
	
	protected void fireStateEvent()
	{
		final FractalStateEvent event = new FractalStateEvent(this, state);
		SwingUtilities.invokeLater(new Runnable()
		{
			@Override
			public void run()
			{
				for (FractalStateListener l : eventListenerList.getListeners(FractalStateListener.class))
				{
					l.fractalStateChanged(event);
				}
			}
		});
	}
	
	protected void pauseTest()
	{
		while (state.equals(FractalState.PAUSED))
		{
			try
			{
				synchronized (thread)
				{
					thread.wait();
				}
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
	
	public boolean restartTest()
	{
		return state.equals(FractalState.RESTARTING);
	}
	
	/**
	 * Override this method to implement computation. You have to check periodically check both if this thread is paused with the {@code pauseTest()} method and
	 * if it must restart with {@code restartTest()}.
	 */
	protected abstract void doComputation();
}
